<script setup>
import useDialogStore from 'stores/dialog.js'
import { h, nextTick, reactive, ref } from 'vue'
import useConnectionStore from 'stores/connections.js'
import { NIcon, NSpace, NText, useThemeVars } from 'naive-ui'
import { ConnectionType } from '@/consts/connection_type.js'
import ToggleFolder from '@/components/icons/ToggleFolder.vue'
import ToggleServer from '@/components/icons/ToggleServer.vue'
import ToggleCluster from '@/components/icons/ToggleCluster.vue'
import { debounce, get, includes, indexOf, isEmpty, split } from 'lodash'
import Config from '@/components/icons/Config.vue'
import Delete from '@/components/icons/Delete.vue'
import Unlink from '@/components/icons/Unlink.vue'
import CopyLink from '@/components/icons/CopyLink.vue'
import Connect from '@/components/icons/Connect.vue'
import { useI18n } from 'vue-i18n'
import useTabStore from 'stores/tab.js'
import Edit from '@/components/icons/Edit.vue'
import { hexGammaCorrection, parseHexColor, toHexColor } from '@/utils/rgb.js'
import IconButton from '@/components/common/IconButton.vue'
import usePreferencesStore from 'stores/preferences.js'

const themeVars = useThemeVars()
const i18n = useI18n()
const connectingServer = ref('')
const connectionStore = useConnectionStore()
const tabStore = useTabStore()
const prefStore = usePreferencesStore()
const dialogStore = useDialogStore()

const expandedKeys = ref([])
const selectedKeys = ref([])

const props = defineProps({
    filterPattern: {
        type: String,
    },
})

const contextMenuParam = reactive({
    show: false,
    x: 0,
    y: 0,
    options: null,
    currentNode: null,
})

const renderIcon = (icon) => {
    return () => {
        return h(NIcon, null, {
            default: () => h(icon),
        })
    }
}
const menuOptions = {
    [ConnectionType.Group]: ({ opened }) => [
        {
            key: 'group_rename',
            label: i18n.t('interface.rename_conn_group'),
            icon: renderIcon(Edit),
        },
        {
            key: 'group_delete',
            label: i18n.t('interface.remove_conn_group'),
            icon: renderIcon(Delete),
        },
    ],
    [ConnectionType.Server]: ({ name }) => {
        const connected = connectionStore.isConnected(name)
        if (connected) {
            return [
                {
                    key: 'server_close',
                    label: i18n.t('interface.disconnect'),
                    icon: renderIcon(Unlink),
                },
                {
                    key: 'server_edit',
                    label: i18n.t('interface.edit_conn'),
                    icon: renderIcon(Config),
                },
                {
                    key: 'server_dup',
                    label: i18n.t('interface.dup_conn'),
                    icon: renderIcon(CopyLink),
                },
                {
                    type: 'divider',
                    key: 'd1',
                },
                {
                    key: 'server_remove',
                    label: i18n.t('interface.remove_conn'),
                    icon: renderIcon(Delete),
                },
            ]
        } else {
            return [
                {
                    key: 'server_open',
                    label: i18n.t('interface.open_connection'),
                    icon: renderIcon(Connect),
                },
                {
                    key: 'server_edit',
                    label: i18n.t('interface.edit_conn'),
                    icon: renderIcon(Config),
                },
                {
                    key: 'server_dup',
                    label: i18n.t('interface.dup_conn'),
                    icon: renderIcon(CopyLink),
                },
                {
                    type: 'divider',
                    key: 'd1',
                },
                {
                    key: 'server_remove',
                    label: i18n.t('interface.remove_conn'),
                    icon: renderIcon(Delete),
                },
            ]
        }
    },
}

/**
 * get mark color of server saved in preferences
 * @param name
 * @return {null|string}
 */
const getServerMarkColor = (name) => {
    const { markColor = '' } = connectionStore.serverProfile[name] || {}
    if (!isEmpty(markColor)) {
        const rgb = parseHexColor(markColor)
        const rgb2 = hexGammaCorrection(rgb, 0.75)
        return toHexColor(rgb2)
    }
    return null
}

const renderLabel = ({ option }) => {
    if (option.type === ConnectionType.Server) {
        const color = getServerMarkColor(option.name)
        if (color != null) {
            return h(
                NText,
                {
                    style: {
                        color,
                        fontWeight: '450',
                    },
                },
                () => option.label,
            )
        }
    }
    return option.label
}

// render horizontal item
const renderIconMenu = (items) => {
    return h(
        NSpace,
        {
            align: 'center',
            inline: true,
            size: 2,
            wrapItem: false,
            wrap: false,
            style: 'margin-right: 5px',
        },
        () => items,
    )
}

const renderPrefix = ({ option }) => {
    const iconTransparency = prefStore.isDark ? 0.75 : 1
    switch (option.type) {
        case ConnectionType.Group:
            const opened = indexOf(expandedKeys.value, option.key) !== -1
            return h(
                NIcon,
                { size: 20 },
                {
                    default: () =>
                        h(ToggleFolder, {
                            modelValue: opened,
                            fillColor: `rgba(255,206,120,${iconTransparency})`,
                        }),
                },
            )
        case ConnectionType.Server:
            const connected = connectionStore.isConnected(option.name)
            const color = getServerMarkColor(option.name)
            const icon = option.cluster === true ? ToggleCluster : ToggleServer
            return h(
                NIcon,
                { size: 20, color: !!!connected ? color : undefined },
                {
                    default: () =>
                        h(icon, {
                            modelValue: !!connected,
                            fillColor: `rgba(220,66,60,${iconTransparency})`,
                        }),
                },
            )
    }
}

const getServerMenu = (connected) => {
    const btns = []
    if (connected) {
        btns.push(
            h(IconButton, {
                tTooltip: 'interface.disconnect',
                icon: Unlink,
                onClick: () => handleSelectContextMenu('server_close'),
            }),
            h(IconButton, {
                tTooltip: 'interface.edit_conn',
                icon: Config,
                onClick: () => handleSelectContextMenu('server_edit'),
            }),
        )
    } else {
        btns.push(
            h(IconButton, {
                tTooltip: 'interface.open_connection',
                icon: Connect,
                onClick: () => handleSelectContextMenu('server_open'),
            }),
            h(IconButton, {
                tTooltip: 'interface.edit_conn',
                icon: Config,
                onClick: () => handleSelectContextMenu('server_edit'),
            }),
            h(IconButton, {
                tTooltip: 'interface.remove_conn',
                icon: Delete,
                onClick: () => handleSelectContextMenu('server_remove'),
            }),
        )
    }
    return btns
}

const getGroupMenu = () => {
    return [
        h(IconButton, {
            tTooltip: 'interface.edit_conn',
            icon: Config,
            onClick: () => handleSelectContextMenu('group_rename'),
        }),
        h(IconButton, {
            tTooltip: 'interface.remove_conn',
            icon: Delete,
            onClick: () => handleSelectContextMenu('group_delete'),
        }),
    ]
}

const renderSuffix = ({ option }) => {
    if (includes(selectedKeys.value, option.key)) {
        switch (option.type) {
            case ConnectionType.Server:
                const connected = connectionStore.isConnected(option.name)
                return renderIconMenu(getServerMenu(connected))
            case ConnectionType.Group:
                return renderIconMenu(getGroupMenu())
        }
    }
    return null
}

const onUpdateExpandedKeys = (keys, option) => {
    expandedKeys.value = keys
}

const onUpdateSelectedKeys = (keys, option) => {
    selectedKeys.value = keys
}

/**
 * Open connection
 * @param name
 * @returns {Promise<void>}
 */
const openConnection = async (name) => {
    try {
        connectingServer.value = name
        if (!connectionStore.isConnected(name)) {
            await connectionStore.openConnection(name)
        }
        // check if connection already canceled before finish open
        if (!isEmpty(connectingServer.value)) {
            tabStore.upsertTab({
                server: name,
            })
        }
    } catch (e) {
        $message.error(e.message)
        // node.isLeaf = undefined
    } finally {
        connectingServer.value = ''
    }
}

const removeConnection = (name) => {
    $dialog.warning(
        i18n.t('dialogue.remove_tip', { type: i18n.t('dialogue.connection.conn_name'), name }),
        async () => {
            connectionStore.deleteConnection(name).then(({ success, msg }) => {
                if (!success) {
                    $message.error(msg)
                }
            })
        },
    )
}

const removeGroup = async (name) => {
    $dialog.warning(i18n.t('dialogue.remove_group_tip', { name }), async () => {
        connectionStore.deleteGroup(name).then(({ success, msg }) => {
            if (!success) {
                $message.error(msg)
            }
        })
    })
}

const expandKey = (key) => {
    const idx = indexOf(expandedKeys.value, key)
    if (idx === -1) {
        expandedKeys.value.push(key)
    } else {
        expandedKeys.value.splice(idx, 1)
    }
}

const nodeProps = ({ option }) => {
    return {
        onDblclick: async () => {
            if (option.type === ConnectionType.Server) {
                openConnection(option.name).then(() => {})
            } else if (option.type === ConnectionType.Group) {
                // toggle expand
                nextTick().then(() => expandKey(option.key))
            }
        },
        onContextmenu(e) {
            e.preventDefault()
            const mop = menuOptions[option.type]
            if (mop == null) {
                return
            }
            contextMenuParam.show = false
            nextTick().then(() => {
                contextMenuParam.options = mop(option)
                contextMenuParam.currentNode = option
                contextMenuParam.x = e.clientX
                contextMenuParam.y = e.clientY
                contextMenuParam.show = true
                selectedKeys.value = [option.key]
            })
        },
    }
}

const renderContextLabel = (option) => {
    return h('div', { class: 'context-menu-item' }, option.label)
}

const handleSelectContextMenu = (key) => {
    contextMenuParam.show = false
    const selectedKey = get(selectedKeys.value, 0)
    if (selectedKey == null) {
        return
    }
    const [group, name] = split(selectedKey, '/')
    if (isEmpty(group) && isEmpty(name)) {
        return
    }
    switch (key) {
        case 'server_open':
            openConnection(name).then(() => {})
            break
        case 'server_edit':
            // ask for close relevant connections before edit
            if (connectionStore.isConnected(name)) {
                $dialog.warning(i18n.t('dialogue.edit_close_confirm'), () => {
                    connectionStore.closeConnection(name)
                    dialogStore.openEditDialog(name)
                })
            } else {
                dialogStore.openEditDialog(name)
            }
            break
        case 'server_dup':
            dialogStore.openDuplicateDialog(name)
            break
        case 'server_remove':
            removeConnection(name)
            break
        case 'server_close':
            connectionStore.closeConnection(name).then((closed) => {
                if (closed) {
                    $message.success(i18n.t('dialogue.handle_succ'))
                }
            })
            break
        case 'group_rename':
            if (!isEmpty(group)) {
                dialogStore.openRenameGroupDialog(group)
            }
            break
        case 'group_delete':
            if (!isEmpty(group)) {
                removeGroup(group)
            }
            break
        default:
            console.warn('TODO: handle context menu:' + key)
    }
}

const findSiblingsAndIndex = (node, nodes) => {
    if (!nodes) {
        return [null, null]
    }
    for (let i = 0; i < nodes.length; ++i) {
        const siblingNode = nodes[i]
        if (siblingNode.key === node.key) {
            return [nodes, i]
        }
        const [siblings, index] = findSiblingsAndIndex(node, siblingNode.children)
        if (siblings && index !== null) {
            return [siblings, index]
        }
    }
    return [null, null]
}

// delay save until drop stopped after 2 seconds
const saveSort = debounce(connectionStore.saveConnectionSorted, 2000, { trailing: true })
const handleDrop = ({ node, dragNode, dropPosition }) => {
    const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(dragNode, connectionStore.connections)
    if (dragNodeSiblings === null || dragNodeIndex === null) {
        return
    }
    dragNodeSiblings.splice(dragNodeIndex, 1)
    if (dropPosition === 'inside') {
        if (node.children) {
            node.children.unshift(dragNode)
        } else {
            node.children = [dragNode]
        }
    } else if (dropPosition === 'before') {
        const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, connectionStore.connections)
        if (nodeSiblings === null || nodeIndex === null) {
            return
        }
        nodeSiblings.splice(nodeIndex, 0, dragNode)
    } else if (dropPosition === 'after') {
        const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(node, connectionStore.connections)
        if (nodeSiblings === null || nodeIndex === null) {
            return
        }
        nodeSiblings.splice(nodeIndex + 1, 0, dragNode)
    }
    connectionStore.connections = Array.from(connectionStore.connections)
    saveSort()
}

const onCancelOpen = () => {
    if (!isEmpty(connectingServer.value)) {
        connectionStore.closeConnection(connectingServer.value)
        connectingServer.value = ''
    }
}
</script>

<template>
    <n-empty
        v-if="isEmpty(connectionStore.connections)"
        :description="$t('interface.empty_server_list')"
        class="empty-content" />
    <n-tree
        v-else
        :animated="false"
        :block-line="true"
        :block-node="true"
        :cancelable="false"
        :data="connectionStore.connections"
        :draggable="true"
        :expanded-keys="expandedKeys"
        :node-props="nodeProps"
        :pattern="props.filterPattern"
        :render-label="renderLabel"
        :render-prefix="renderPrefix"
        :render-suffix="renderSuffix"
        :selected-keys="selectedKeys"
        class="fill-height"
        virtual-scroll
        @drop="handleDrop"
        @update:selected-keys="onUpdateSelectedKeys"
        @update:expanded-keys="onUpdateExpandedKeys" />

    <!-- status display modal -->
    <n-modal :show="connectingServer !== ''" transform-origin="center">
        <n-card
            :bordered="false"
            :content-style="{ textAlign: 'center' }"
            aria-model="true"
            role="dialog"
            style="width: 400px">
            <n-spin>
                <template #description>
                    <n-space vertical>
                        <n-text strong>{{ $t('dialogue.opening_connection') }}</n-text>
                        <n-button secondary size="small" :focusable="false" @click="onCancelOpen">
                            {{ $t('dialogue.interrupt_connection') }}
                        </n-button>
                    </n-space>
                </template>
            </n-spin>
        </n-card>
    </n-modal>

    <!-- context menu -->
    <n-dropdown
        :animated="false"
        :options="contextMenuParam.options"
        :render-label="renderContextLabel"
        :show="contextMenuParam.show"
        :x="contextMenuParam.x"
        :y="contextMenuParam.y"
        placement="bottom-start"
        trigger="manual"
        @clickoutside="contextMenuParam.show = false"
        @select="handleSelectContextMenu" />
</template>

<style lang="scss" scoped>
@import '@/styles/content';
</style>
